本系列,原本由之前挖的坑,现在来填。之间在讲述动态代理,谈及mybatis、dubbo的源码,大家也可以前往了解,点击查看。手写dubbo,是为了更好的理解dubbo源码原理,顺便和大家一起看看dubbo实现过程。

准备

本系列博客,主要实现dubbo的服务调用,基于netty/http+zookeeper/redis。所以你有必要准备zookeeper和redis的环境。这个自行百度,这里讲述毫无意义。
毕竟是剖析dubbo源码,建议大家还是有必要将源码下载下来阅读一下,以及本博客中所有代码,可以通过下面链接下载。

dubbo介绍

Apache Dubbo |ˈdʌbəʊ| is a high-performance, light weight, java based RPC framework. Dubbo offers three key functionalities, which include interface based remote call, fault tolerance & load balancing, and automatic service registration & discovery.

这段介绍来自dubbo官网,打开官网就能看到,我认识的单词也不多,就把认识的标粗了。它告诉我们dubbo实现了三个功能

  1. 远程接口调用
  2. 容错和负载均衡
  3. 服务自动注册和发现

那么我们手写dubbo的话,自然也要实现官网中介绍的三个功能,也就是本系列博客的重点。与之对应的,官网还提供了一张图,描述了这个三功能如何配合使用的。如下

dubbo架构

简单看一下这张图,provider在registry中注册自己的服务,consumer从register中subscribe服务。consumer拿到服务后,直接invoke对应的provider。其中dubbo还提供监控的功能,也就是monitor。那么我们接下来顺着这张图揣测一下dubbo三个功能的原理

揣测dubbo原理

先回顾一下dubbo的诞生的场景。dubbo作为一款分布式服务治理框架,这是没有异议的。它帮助我们完成了应用之间的调用。那么在没有它之前我们想在两个系统之间调用对方的服务是怎么做的呢?

从单体应用系统开始说,我们现在有两个类Person、Dog,假如我们需要Person的对象调用Dog中walk()方法。那么我们的做法肯定都是new Dog().walk();。这个已经是习以为常的了,没什么好说的,但是我们为什么能new一个Dog对象,因为在我们系统中存在Dog的class文件,我们才能new。那么假如这两个类存在于两个系统中,它们需要互相调用,我们是怎么做的呢?存在以下两种解决方案

  1. A(provider)应用提供jar供B(consumer)应用调用,此时A应用的class文件会加载到B系统中,B可以直接new出A的对象。
  2. 通过网络调用,B(consumer)应用通过网络告诉A(provider)要调用那个方法,A调用完后将结果返回给B。

第一种方案,没什么好说的。而dubbo也就是第二中方案的实现框架。那么一起来猜测一下dubbo怎么做到调用本方法那样去调用远程服务的。

远程接口调用

作为一款rpc框架,主要任务就是远程调用,那么我们从这里入手肯定是没错的。现在需求是A系统提供一个服务供B系统调用。那我们先确认两个概念,A也就是provider,B为consumer,那么一个个的来分析。

作为provider需要给别人提供服务,肯定是要给别人调用的。比如我们写的restful接口,别人使用http来调用我们的接口,那么作为provider我们也需要这样的程序。这里我们可以选择tomcat提供http协议的服务,也可以通过socket供客户端来连接。不管怎么样,这两种实现方式肯定是可以供consumer调用的。

provider提供调用的服务,我们可以解决了,那么客户端调用过来时候,provider怎么知道要执行哪个方法呢?这是你可能会想这个可以由consumer在调用时传几个参数告诉provider。对的。事实上dubbo也确实是这样做的,它会传输类似以下对象的数据给provider

1
2
3
4
5
6
class Message {
public String clazz;
public String method;
public String types;
public String params;
}

这时,怎么调用解决了,要调用哪个服务也解决了,那么这时由provider通过反射执行对应方法。至此,我觉得provider的实现方式,这个方案是可行的。
接下来看consumer,consumer要做的事情就是调用provider的服务,如果provider提供的是http接口,那么我就通过httpClient来调用;如果provider提供的socket,那么就用socket来连接,这都是可以实现的。但是会存在一个问题,就是我们在调用是写HttpClient代码调用时,对不起,哥们,我觉得我是在调用服务端代码,这不是我想要的。而这时dubbo提供的解决方案就是动态代理,consumer像调用本地方法那样去调用某一个服务,由代理类去完成远程调用的过程。

小小的总结一下,dubbo远程调用,由consumer在代理类中通过http/socket去调用provider提供的服务。

服务自动注册和发现

服务自动注册和发现,也就是图中registry。consumer需要去远程调用provider,那么肯定是要有一个ip和端口号的,要不然你怎么调用。当provider提供的服务比较多,或者同一个服务有多个provider时,它们都分别有自己的ip和端口号,那么consumer怎么去维护呢?这时注册中就显得尤为重要。

那么dubbo的注册和发现是怎么做的呢?其实要完成上面的需要,我们是不是只要有一个统一管理的地方就行了,对不对。最不济我弄一个Map<service, provider>,这样把它存下来就好了,provider启动时往这个map里put服务当作服务注册的过程,consumer要使用时,到这个map中取就好了。

上面的方案是可以实现的,但是dubbo提供的解决方案是zookeeper/redis,但是它们中间存储的数据结构类似Map中的数据,只是存储数据的载体不是Map而已,这个我们在写代码中会得以体现。

容错和负载均衡

博客中实现的负载均衡,只是从众多的provider中简单的random一个出来,其余的负载均衡选择算法,可以参照dubbo源码实现。

总结

根据我们上面的推理,dubbo原理总结为:provider提供服务,这个服务可以是http/socket,给consumer来调用,然后通过反射的方式执行对应的方法,将结果返回给consumer。为了让这个过程无感知,consumer使用动态代理的过程,完成调用过程。

目录结构

  1. 手写dubbo框架1-基本原理
  2. 手写dubbo框架2-服务治理和发现
    2.1 分布式锁(redis+zookeeper)
  3. 手写dubbo框架3-spi治理redis和zk
    3.1 spi详解
  4. 手写dubbo框架4-远程调用\动态代理(netty+http)